<?php

namespace app\common\lib;

/**
 * 雪花算法
 */
class Snowflake
{
    //开始时间,固定一个小于当前时间的毫秒数即可
    const twepoch = 1474992000000; //2016/9/28 0:0:0

    //机器标识占的位数
    const workerIdBits = 6;

    //数据中心标识占的位数
    const datacenterIdBits = 5;

    //毫秒内自增数点的位数
    const sequenceBits = 12;

    // 提示：这里需要指定一个机器id/机房id，机器多的时候，人力成本大且容易出错
    // 优化方案1：参考百度的UidGenerator
    protected $workId = 0; //机器id
    protected $datacenterId = 0; //机房id

    static $lastTimestamp = -1;
    static $sequence = 0; // 一毫秒内生成的多个id的序号

    /**
     * @throws \Exception
     */
    function getInstance($workId = 0, $datacenterId = 0)
    {
        //机器ID范围判断 机房id不能超过32，不能小于0
        $maxWorkerId = -1 ^ (-1 << self::workerIdBits);
        if ($workId > $maxWorkerId || $workId < 0) {
            throw new \Exception("workerId can't be greater than " . $maxWorkerId . " or less than 0");
        }
        //数据中心ID范围判断 机器id不能超过32，不能小于0
        $maxDatacenterId = -1 ^ (-1 << self::datacenterIdBits);
        if ($datacenterId > $maxDatacenterId || $datacenterId < 0) {
            throw new \Exception("datacenter Id can't be greater than " . $maxDatacenterId . " or less than 0");
        }
        //赋值
        $this->workId = $workId;
        $this->datacenterId = $datacenterId;
    }

    /**
     * 生成唯一ID
     * @throws \Exception
     */
    public function nextId()
    {
        $timestamp = $this->getNewStamp();
        $lastTimestamp = self::$lastTimestamp;
        //判断时钟是否正常
        if ($timestamp < $lastTimestamp) {
            throw new \Exception("Clock moved backwards.  Refusing to generate id for %d milliseconds", ($lastTimestamp - $timestamp));
        }

        //生成唯一序列: 同一个毫秒内，又发送了一个请求生成一个id, seqence序号递增1
        if ($lastTimestamp == $timestamp) {
            $sequenceMask = -1 ^ (-1 << self::sequenceBits);
            //位运算保证sequence在4096这个范围
            self::$sequence = (self::$sequence + 1) & $sequenceMask;
            if (self::$sequence == 0) {
                $timestamp = $this->getNextMillis($lastTimestamp);
            }
        } else {
            self::$sequence = 0;
        }

        // 记录一下最近一次生成id的时间戳，单位是毫秒
        self::$lastTimestamp = $timestamp;

        //时间毫秒/数据中心ID/机器ID,要左移的位数
        $timestampLeftShift = self::sequenceBits + self::workerIdBits + self::datacenterIdBits;
        $datacenterIdShift = self::sequenceBits + self::workerIdBits;
        $workerIdShift = self::sequenceBits;

        //组合4段数据返回: 时间戳.数据标识.工作机器.序列
        return (($timestamp - self::twepoch) << $timestampLeftShift) |
            ($this->datacenterId << $datacenterIdShift) |
            ($this->workId << $workerIdShift) | self::$sequence;
    }

    //取下一毫秒
    protected function getNextMillis($lastTimestamp)
    {
        $timestamp = $this->getNewStamp();
        while ($timestamp <= $lastTimestamp) {
            $timestamp = $this->getNewStamp();
        }
        return $timestamp;
    }

    //取当前时间毫秒
    protected function getNewStamp()
    {
        return (float)sprintf("%.0f", microtime(true) * 1000);
    }
}